﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WaveLib;


namespace BasicWave
{

    /// <summary>
    /// CLasse principale della libreria! 
    /// Si occupa di fornire dati al buffer della scheda audio prelevandoli 
    /// da una fifo interna. Gestisce la miscelazione dei vari canali
    /// e dei vari aux.
    /// </summary>
    public class CMasterChannel :IDisposable
    {
        WaveFormat format;

        private WaveLib.WaveOutPlayer m_Player;

        private byte[] m_PlayBuffer;

        const int BUFFER_MASTER = 4096;


        //TODO: il delay sulla ricezione del microfono è molto fastidioso...
        //una soluzione potrebbe essere di non mandare i dati alla scheda audio
        //ma soltanto al export mp3 

        //QUESTO è la dimensione del buffer che viene richiesto ogni volta dalla scheda audio
        //più è alto il valore e più c'è lag fra la ripresa e la riproduzione
        const int BUFFER_SIZE = 32768;


        //tiene la lista degli aux registrati
        List<IAuxChannel> _aux_list;

        //lista che tiene tutti i canali che si sono registrati al master
        //questa lista è usata per sapere a quali canali inviare la richiesta
        //di fillchunk essenziale per miscelare e sincronizzare tutti i canali
        List<IAudioChannel> _chans_list;


        /// <summary>
        /// questo buffer viene utilizzato come buffer per i dati che 
        /// acquisiscono audio  come il microfono o un decodificatore 
        /// mp3 
        /// La funzione filler preleva i dati da questo buffer
        /// per sopperire alla richiesta dell so
        /// </summary>
        private WaveLib.FifoStream m_Fifo = new WaveLib.FifoStream();


        //costruttore
        public CMasterChannel()
        {

            //lista che conterrà tutte le classi iAuxChannel
            _aux_list = new List<IAuxChannel>();
            _chans_list = new List<IAudioChannel>();

            WaveLib.WaveFormat fmt = new WaveLib.WaveFormat(44100, 16, 2);
            m_Player = new WaveLib.WaveOutPlayer(-1, fmt, BUFFER_SIZE, 3, new WaveLib.BufferFillEventHandler(Filler));

        }


        //filler function is a callback called by operating system
        //inform us that is available a buffer (data) of size dimension
        //we must fill data that will go in play as soon as possible

        /// <summary>
        /// Funzione IMPORTANTISSIMA..èE'0 IL CENTRO DI TUTTa la     LIBRERIA
        /// 
        /// Funzione che viene chiamata dal sistema operativo
        /// Ci dice che dobbiamo riempire il buffer prima che vada
        /// in esecuzione dalla scheda....
        /// 
        /// </summary>
        ///  <remarks>Funziona!</remarks>
        /// <param name="data"></param>
        /// <param name="size"></param>
        private void Filler(IntPtr data, int size)
        {
            if (m_PlayBuffer == null || m_PlayBuffer.Length < size)
                m_PlayBuffer = new byte[size];


            if (m_Fifo.Length >= size)
                m_Fifo.Read(m_PlayBuffer, 0, size);
            else
                for (int i = 0; i < m_PlayBuffer.Length; i++)
                    m_PlayBuffer[i] = 0;

            //invia i dati prelevati dal master buffer al canale aux
            SendDataToAux(m_PlayBuffer, m_PlayBuffer.Length);


            //questa funzione è importante poichè rasferisce i byte allocati
            //in un contesto gestito in uno non gestito....area che sarà
            //utilizzata dalla scheda audio per inviare dati al D/A
            System.Runtime.InteropServices.Marshal.Copy(m_PlayBuffer, 0, data, size);



            //è la funzione che miscela i vari canali..invia la chiamata a Fillchunk a tutti
            //i canali e si occupa di inserire il risoltutato ottenuto 
            //nel master buffer
            MixChans();

        }




        /// <summary>
        /// 
        //è la funzione che miscela i vari canali..invia la chiamata a Fillchunk a tutti
        //i canali e si occupa di inserire il risoltutato ottenuto 
        ///nel master buffer
        /// <remarks>Funziona!</remarks>
        /// </summary>
        private void MixChans()
        {
            
            //TODO: da fare le prove con i float

            byte[] byte_buff_gen = new byte[BUFFER_SIZE];

            short[] temp = new short[BUFFER_SIZE / 2];
            short[] temp2 = new short[BUFFER_SIZE / 2];

            for (int i = 0; i < _chans_list.Count; i++)
            {
                byte[] byte_buff_temp = new byte[BUFFER_SIZE];
                
                IAudioChannel chan = (IAudioChannel)_chans_list[i];


                //invia la chiamata al chanel permettendogli di rimpeire
                //il buffer con i suoi dati
                chan.FillChunk(ref byte_buff_temp, BUFFER_SIZE);

                
                //funziona
                Buffer.BlockCopy(byte_buff_temp, 0, temp, 0, BUFFER_SIZE);




                //funziona...da valutare se è meglio con il float..anzi..di quanto è meglio con il float

                //miscelo il buff standard con quello appena preso
                //ma lo devo fare trasformando i valori in short..prendendone 2 per volta
                for (int j = 0; j < (BUFFER_SIZE / 2); j++)
                {

                    temp2[j] = (short)((temp[j] + temp2[j]) - ((temp[j] * temp2[j]) / 16384));

                }




                //funziona, copia il blocco di short in un array di byte
                Buffer.BlockCopy(temp2, 0, byte_buff_gen, 0, BUFFER_SIZE);

            }


            //accoda ii dati miscelati al buffer
            EnquedRawData(ref byte_buff_gen, 0, BUFFER_SIZE);
        }



        /// <summary>
        /// funzione che accoda al main buffer i dati passati
        /// Questa funzione è importante perchè è dal main buffer che la funzione Filler
        /// andrà a prelevare i dati che saranno inviato alla scheda audio
        /// </summary>
        ///  <remarks>Funziona!</remarks>
        /// <param name="aByte"></param>
        /// <param name="aSize"></param>
        public void EnquedRawData(ref byte[] aByte, int ofe, int count)
        {
            m_Fifo.Write(aByte, ofe, count);

        }


        #region "Funzioni Chans"
        
        /// <summary>
        /// Registra un canale nel master
        /// Il canale deve aver implementato l'0interfaccia IAudioChannel
        /// </summary>
        /// <param name="audio"></param>
        public void RegisteredChan(IAudioChannel audio)
        {
            _chans_list.Add(audio);
        }

        /// <summary>
        /// Rimuove un canale dalla lista di quelli associati al master
        /// Il canale deve aver implementato l'0interfaccia IAudioChannel
        /// </summary>
        /// <param name="audio"></param>
        public void UnregisteredChan(IAudioChannel audio)
        {
            _chans_list.Remove(audio);
        }

        #endregion

        #region "Funzioni aux"


        /// <summary>
        /// viene chiamata dalla funzione filler
        /// invia una copia dei dati che vengono inviati alla scheda audio
        /// anche agli aux che si sono registrati nel master
        /// </summary>
        /// <param name="aByte"></param>
        /// <param name="aSize"></param>
        private void SendDataToAux(byte[] aByte, int aSize)
        {
            for (int i = 0; i < _aux_list.Count; i++)
            {
                IAuxChannel aux = (IAuxChannel)_aux_list[i];
                aux.RawData(aByte, aSize);
            }
        }


        /// <summary>
        /// accetta la registrazione di una classe come aux
        /// classe che deve implementare l'interfaccia IAuxChannel
        /// </summary>
        /// <param name="iAux"></param>
        public void RegisteredAux(IAuxChannel iAux)
        {
            _aux_list.Add(iAux);
            
        }

        /// <summary>
        /// Rimuove dalla lista un aux precedentemente registrato
        /// </summary>
        /// <param name="iAux"></param>
        public void UnregisteredAux(IAuxChannel iAux)
        {
            _aux_list.Remove(iAux);
        }
        
        #endregion




     

        public void Dispose()
        {
            


            if (m_Player != null)
                try
                {
                    m_Player.Dispose();
                }
                finally
                {
                    m_Player = null;
                }
        
            m_Fifo.Flush(); // clear all pending data

        }

    }
}
